package flca.android.util;

import java.util.ArrayList;
import java.util.List;

import android.app.Activity;
import android.content.Context;
import android.location.Criteria;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.location.LocationProvider;
import android.os.Bundle;
import android.util.Log;

public class LocationMgr implements LocationListener
{
	private static final String TAG = "VoiceNav";
	
	private static LocationMgr sInstance;
	
	private LocationManager mLocationManager;
	private List<LocationListener> mLocationListeners = new ArrayList<LocationListener>();
	private int mUpdateMsec = 500;

	// Holds the most up to date location.
	private Location mCurrBestLocation;
	// Set to false when location services are unavailable.
	private boolean locationAvailable = true;

	public static LocationMgr getinstance() 
	{
		if (sInstance == null) {
			throw new RuntimeException("initialize not called");
		}
		return sInstance;
	}
	
	public static LocationMgr initialize(Activity aActv, LocationListener aLocListener[])
	{
		sInstance = new LocationMgr(aActv, aLocListener);
		return sInstance;
	}
	
	public void destroy() 
	{
		for (int i=mLocationListeners.size()-1; i >= 0; i--) {
			mLocationListeners.remove(i);
		}
		
		if (mLocationManager != null) {
			mLocationManager = null;
		}
	}
	
	private LocationMgr(Activity aActv, LocationListener aLocListener[]) 
	{
		this(aActv, aLocListener, 500);
	}

	
	public LocationMgr(Activity aActv, LocationListener aLocationListener[], int mUpdateMsec) 
	{
		super();
		for (int i = 0; i < aLocationListener.length; i++) {
			this.mLocationListeners.add(aLocationListener[i]);
		}

		this.mUpdateMsec = mUpdateMsec;
		
		mLocationManager =  (LocationManager) aActv.getSystemService(Context.LOCATION_SERVICE);
		initialize();
	}
	
	public void addListener(LocationListener aListener) {
		mLocationListeners.add(aListener);
	}

	public void removeListener(LocationListener aListener) {
		mLocationListeners.remove(aListener);
	}

	private void initialize() 
	{
		// Initialize criteria for location providers
		Criteria fine = new Criteria();
		fine.setAccuracy(Criteria.ACCURACY_FINE);
		Criteria coarse = new Criteria();
		coarse.setAccuracy(Criteria.ACCURACY_COARSE);

		mCurrBestLocation = mLocationManager.getLastKnownLocation(mLocationManager.getBestProvider(fine, true));

		// Will keep updating about every 500 ms until
		// accuracy is about 1000 meters to get quick fix.
//		mLocationManager.requestLocationUpdates(
//				mLocationManager.getBestProvider(coarse, true), 250, 0, //mUpdateMsec, 1000,
//				this);
		// Will keep updating about every 500 ms until
		// accuracy is about 50 meters to get accurate fix.
		mLocationManager.requestLocationUpdates(
				mLocationManager.getBestProvider(fine, true), 250, 0, //mUpdateMsec, 100,
				this);
	}
	
	//----- LocationListener events ---*/
	@Override
	public void onLocationChanged(Location location)
	{
		if (isBetterLocation(location)) {
			mCurrBestLocation = location;
		}
		
		if (mLocationListeners != null) {
			for (LocationListener listener : mLocationListeners) {
				if (listener != null) {
					listener.onLocationChanged(mCurrBestLocation);
				}
			}
		}
	}

	@Override
	public void onProviderDisabled(String provider)
	{
		Log.w(TAG, "onProviderDisabled " + provider);
	}

	@Override
	public void onProviderEnabled(String provider)
	{
		Log.w(TAG, "onProviderEnabled " + provider);
	}

	@Override
	public void onStatusChanged(String provider, int status, Bundle extras)
	{
		Log.w(TAG, "onStatusChanged " + status);
		switch (status) {
		case LocationProvider.OUT_OF_SERVICE:
		case LocationProvider.TEMPORARILY_UNAVAILABLE:
			locationAvailable = false;
			break;
		case LocationProvider.AVAILABLE:
			locationAvailable = true;
		}
	}


	public Location getCurrentLocation()
	{
		return mCurrBestLocation;
	}

	public static void logLocationUpdate(String aTag, Location aLoc)
	{
	}
	
	
	private static final int TWO_MINUTES = 1000 * 60 * 2;

	/** Determines whether one Location reading is better than the current Location fix
	  * @param location  The new Location that you want to evaluate
	  * @param mCurrBestLocation  The current Location fix, to which you want to compare the new one
	  */
	protected boolean isBetterLocation(Location location) 
	{
	    if (mCurrBestLocation == null) {
	        // A new location is always better than no location
	        return true;
	    }

	    // Check whether the new location fix is newer or older
	    long timeDelta = location.getTime() - mCurrBestLocation.getTime();
	    boolean isSignificantlyNewer = timeDelta > TWO_MINUTES;
	    boolean isSignificantlyOlder = timeDelta < -TWO_MINUTES;
	    boolean isNewer = timeDelta > 0;

	    // If it's been more than two minutes since the current location, use the new location
	    // because the user has likely moved
	    if (isSignificantlyNewer) {
	        return true;
	    // If the new location is more than two minutes older, it must be worse
	    } else if (isSignificantlyOlder) {
	        return false;
	    }

	    // Check whether the new location fix is more or less accurate
	    int accuracyDelta = (int) (location.getAccuracy() - mCurrBestLocation.getAccuracy());
	    boolean isLessAccurate = accuracyDelta > 0;
	    boolean isMoreAccurate = accuracyDelta < 0;
	    boolean isSignificantlyLessAccurate = accuracyDelta > 200;

	    // Check if the old and new location are from the same provider
	    boolean isFromSameProvider = isSameProvider(location.getProvider(),
	            mCurrBestLocation.getProvider());

	    // Determine location quality using a combination of timeliness and accuracy
	    if (isMoreAccurate) {
	        return true;
	    } else if (isNewer && !isLessAccurate) {
	        return true;
	    } else if (isNewer && !isSignificantlyLessAccurate && isFromSameProvider) {
	        return true;
	    }
	    return false;
	}

	/** Checks whether two providers are the same */
	private boolean isSameProvider(String provider1, String provider2) {
	    if (provider1 == null) {
	      return provider2 == null;
	    }
	    return provider1.equals(provider2);
	}

	
}
